﻿using System;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AIProject.Search;

namespace AIProject.AI
{
    class IDAlphaBetaAITransGameAgent : GameAgent
    {
        private Dictionary<Pylos.State, NodeData> dict;
        private Boolean isWhite;
        private AlphaBetaTrans ab;
        private NodeInfo nodeInfo;
        private Pylos.Action maxAction;
        private Pylos.Action minAction;
        double alpha;
        double beta;
        private Percept p;
        private Boolean stop;

        public int Nodes
        {
            get
            {
                return (ab.NodeCount + ab.ExtNodes);
            }
            set
            {
            }
        }

        public IDAlphaBetaAITransGameAgent(Boolean isWhite)
        {
            this.isWhite = isWhite;
            this.stop = false;
            this.dict = new Dictionary<Pylos.State, NodeData>();
        }

        public Pylos.Action getAction(Percept p)
        {
            this.p = p;
            this.stop = false;
            this.nodeInfo = new NodeInfo(1, isWhite);
            this.ab = new AlphaBetaTrans(dict, nodeInfo, 4);

            if (this.dict.Count > 50000)
            {
                this.dict = new Dictionary<Pylos.State, NodeData>();
            }

            Stopwatch sw = new Stopwatch();
            Thread t = new Thread(doGetAction);

            t.Start();
            sw.Start();

            while (!t.IsAlive && sw.ElapsedMilliseconds < 3000) { }

            sw.Stop();

            if (sw.ElapsedMilliseconds > 3000)
            {
                throw new InvalidOperationException("The getActions() Thread took too long to start");
            }
            else
            {
                sw.Reset();
            }

            sw.Start();

            while (sw.ElapsedMilliseconds < 3000 && t.IsAlive) { }

            ab.Stop = true;
            this.stop = true;

            return (isWhite ? maxAction : minAction);
        }

        public void reset()
        {
            this.dict = new Dictionary<Pylos.State, NodeData>();
        }

        private void doGetAction()
        {
            Pylos.Action recommended = null;

            Node root = new Node(p.State);
            List<Pylos.Action> actions = p.State.getActions();

            while (nodeInfo.DepthLimit <= 4)
            {
                if (stop)
                {
                    return;
                }

                double childValue;
                alpha = Double.NegativeInfinity;
                beta = Double.PositiveInfinity;

                if (dict.ContainsKey(root.State))
                {
                    if (stop)
                    {
                        return;
                    }

                    NodeData temp = dict[root.State];
                    recommended = temp.Action;

                    if (this.isWhite)
                    {
                        alpha = Math.Max(alpha, ab.minValue(root, alpha, beta, false, false));
                        maxAction = recommended;
                    }
                    else
                    {
                        beta = Math.Min(beta, ab.maxValue(root, alpha, beta, false, false));
                        minAction = recommended;
                    }
                }

                foreach (Pylos.Action arc in actions)
                {
                    if (stop)
                    {
                        return;
                    }

                    if (!arc.Equals(recommended))
                    {
                        Node child = (Node)root.clone();
                        child.update(arc);

                        if (this.isWhite)
                        {
                            if (nodeInfo.isTerminal(child)
                                    || child.State.getActions().Count == 0
                                    || child.Cost >= nodeInfo.DepthLimit)
                            {
                                childValue = nodeInfo.utility(child);
                                ab.NodeCount++;
                            }
                            else
                            {
                                childValue = ab.minValue(child, alpha, beta, false, false);
                            }

                            if (childValue >= alpha)
                            {
                                alpha = childValue;
                                maxAction = arc;
                            }
                        }
                        else
                        {
                            if (nodeInfo.isTerminal(child)
                                    || child.State.getActions().Count == 0
                                    || child.Cost >= nodeInfo.DepthLimit)
                            {
                                ab.NodeCount++;
                                childValue = nodeInfo.utility(child);
                            }
                            else
                            {
                                childValue = ab.maxValue(child, alpha, beta, false, false);
                            }

                            if (childValue <= beta)
                            {
                                beta = childValue;
                                minAction = arc;
                            }
                        }
                    }
                }

                //Console.WriteLine(ab.NodeCount + " for " + nodeInfo.DepthLimit + " and ext Nodes " + ab.ExtNodes);
                //Console.WriteLine("Added " + ab.Count + " for " + nodeInfo.DepthLimit);
                nodeInfo.DepthLimit++;

                if (nodeInfo.DepthLimit < 5)
                {
                    ab.reset();
                }
            }

            if (isWhite)
            {
                add(root, maxAction);
            }
            else
            {
                add(root, minAction);
            }

            //Console.WriteLine(dict.Count);
        }

        private void add(Node node, Pylos.Action a)
        {
            if (!dict.ContainsKey(node.State))
            {
                dict[node.State] = new NodeData(a, node.Cost);
            }
            else
            {
                NodeData temp = dict[node.State];

                if (node.Cost <= temp.Depth)
                {
                    temp.Action = a;
                    temp.Depth = node.Cost;
                }
            }
        }
    }
}
